﻿/// @file kdatetime.c
#include "stdafx.h"
#include "sbcs.h"
#include "extend/sbvalues.h"
#include "kstub_math.h"
#if _SB_UNIX_
#include "sys/time.h"
#endif

//////////////////////////////////////////////////////////////////////////
// 날짜 시간

/// 로컬 시간으로 변화.
/// @date 2013-12-22
/// @param [out] ptm 널값이 아니면, tm 형식.
/// @param [in] ptt  time_t형식.
void k_localtime(struct tm* ptm, const time_t* ptt)
{
#if _MSC_VER
	localtime_s(ptm, ptt);
#elif __GNUC__
	localtime_r(ptt, ptm);
#else
	if (ptm)
		*ptm = *localtime(ptt);
#endif
}

/// 시간값 더하기.
/// @date 2013-12-22
/// @param [in,out] tv  자신 참조 포인터.
/// @param microseconds microseconds.
void k_timeval_add_microsecond(kTimeVal* tv, kint microseconds)
{
	k_return_if_fail(tv->tv_usec >= 0 && tv->tv_usec < K_CONST_USEC_PER_SEC);

	if (microseconds < 0)
	{
		microseconds *= -1;
		tv->tv_usec -= microseconds % K_CONST_USEC_PER_SEC;
		tv->tv_sec -= microseconds / K_CONST_USEC_PER_SEC;

		if (tv->tv_usec < 0)
		{
			tv->tv_usec += K_CONST_USEC_PER_SEC;
			tv->tv_sec--;
		}
	}
	else
	{
		tv->tv_usec += microseconds % K_CONST_USEC_PER_SEC;
		tv->tv_sec += microseconds / K_CONST_USEC_PER_SEC;

		if (tv->tv_usec >= K_CONST_USEC_PER_SEC)
		{
			tv->tv_usec -= K_CONST_USEC_PER_SEC;
			tv->tv_sec++;
		}
	}
}

/// 시간값 더하기.
/// @date 2013-12-22
/// @param 자기 자신 참조 포인터.
/// @param milliseconds 밀리초.
void k_timeval_add_millisecond(kTimeVal* tv, kint milliseconds)
{
	k_timeval_add_microsecond(tv, milliseconds * 1000);
}

/// 현재 시간.
/// @date 2013-12-22
/// @param [out] tv 시간.
void k_timeval_now(kTimeVal* tv)
{
#if _SB_WINDOWS_
	union
	{
		FILETIME	ft;
		kulong		l;
	} t;

	k_return_if_fail(tv != NULL);

	GetSystemTimeAsFileTime(&t.ft);

	t.l -= K_CONST_LONG(116444736000000000);
	t.l /= 10;

	tv->tv_sec = (kint)(t.l / 1000000);
	tv->tv_usec = (kint)(t.l % 1000000);
#else
	struct timeval v;

	gettimeofday(&v, NULL);
	tv->tv_sec = v.tv_sec;
	tv->tv_usec = v.tv_usec;
#endif
}

/// 현재 날짜.
/// @date 2013-12-22
/// @param [out] dt 날짜.
void k_dateval_now(kDateVal* dt)
{
#if _SB_WINDOWS_
	SYSTEMTIME st;

	k_return_if_fail(dt != NULL);

	GetLocalTime(&st);
	dt->year = st.wYear;
	dt->month = (kbyte)st.wMonth;
	dt->day = (kbyte)st.wDay;
	dt->dow = (kbyte)st.wDayOfWeek;
	dt->hour = (kbyte)st.wHour;
	dt->minute = (kbyte)st.wMinute;
	dt->second = (kbyte)st.wSecond;
	dt->millisecond = st.wMilliseconds;
	dt->valid = 0;
#else
	struct timeval tv;
	struct tm tm;

	k_return_if_fail(dt != NULL);

	gettimeofday(&tv, NULL);
	k_localtime(&tm, &tv.tv_sec);
	dt->year = tm.tm_year + 1900;
	dt->month = (kbyte)tm.tm_mon + 1;
	dt->day = (kbyte)tm.tm_mday;
	dt->dow = (kbyte)tm.tm_wday;
	dt->hour = (kbyte)tm.tm_hour;
	dt->minute = (kbyte)tm.tm_min;
	dt->second = (kbyte)tm.tm_sec;
	dt->millisecond = tv.tv_usec / 1000;
	dt->valid = 0;
#endif
}

/// 지금.
/// @date 2013-12-22
/// @param [out] dt 날짜 시간.
void k_now(kDateTime* dt)
{
#if _SB_WINDOWS_
	SYSTEMTIME st;

	k_return_if_fail(dt != NULL);

	GetLocalTime(&st);
	dt->year = st.wYear;
	dt->month = st.wMonth;
	dt->day = st.wDay;
	dt->dow = st.wDayOfWeek;
	dt->hour = st.wHour;
	dt->minute = st.wMinute;
	dt->second = st.wSecond;
	dt->millisecond = st.wMilliseconds;
#else
	struct timeval tv;
	struct tm tm;

	k_return_if_fail(dt != NULL);

	gettimeofday(&tv, NULL);
	k_localtime(&tm, &tv.tv_sec);
	dt->year = tm.tm_year + 1900;
	dt->month = tm.tm_mon + 1;
	dt->day = tm.tm_mday;
	dt->dow = tm.tm_wday;
	dt->hour = tm.tm_hour;
	dt->minute = tm.tm_min;
	dt->second = tm.tm_sec;
	dt->millisecond = tv.tv_usec / 1000;
#endif
}

/// 초를 시간으로.
/// @date 2013-12-22
/// @param sec	    초.
/// @param [out] dt 날짜 시간.
void k_stodt(double sec, kDateTime* dt)
{
	kuint ns = (kuint)sec;
	double m;

	dt->hour = ns / 3600;
	dt->minute = (ns % 3600) / 60;
	dt->second = (ns % 60);

	m = (double)stub_math_floor(sec);
	m = sec - m;
	m *= 1000.0;
	dt->millisecond = (kuint)m;
}

/// 밀리초를 시간으로.
/// @date 2013-12-22
/// @param msec	    밀리초.
/// @param [out] dt 날짜 시간.
void k_mstodt(kuint msec, kDateTime* dt)
{
	kuint ns = msec / 1000;

	dt->hour = ns / 3600;
	dt->minute = (ns % 3600) / 60;
	dt->second = (ns % 60);
	dt->millisecond = msec - (ns * 1000);
}

/// 날자를 문자열로.
/// @date 2013-12-22
/// @param dt		    날짜 시간. 널값이면 현재 시간을 사용.
/// @param mtd		    메소드.
/// @param [in,out] buf 널값이 아니면, 버퍼.
/// @param size		    크기.
void k_dts(const kDateTime* dt, kint mtd, char* buf, kint size)
{
	kint m;
	kDateTime tmp;

	k_return_if_fail(buf != NULL && size > 0);

	if (!dt)
	{
		k_now(&tmp);
		dt = &tmp;
	}

	m = mtd & 0xFF;

	if (m == KDTS_DATE)
	{
		k_snprintf(buf, size, "%04d-%02d-%02d",
			dt->year, dt->month, dt->day);
	}
	else if (m == KDTS_TIME)
	{
		if (mtd&KDTS_MILLISECOND)
		{
			k_snprintf(buf, size, "%02d:%02d:%02d.%03d",
				dt->hour, dt->minute, dt->second, dt->millisecond);
		}
		else
		{
			k_snprintf(buf, size, "%02d:%02d:%02d",
				dt->hour, dt->minute, dt->second);
		}
	}
	else //if (m == KDTS_DATETIME)
	{
		if (mtd&KDTS_MILLISECOND)
		{
			k_snprintf(buf, size, "%04d-%02d-%02d %02d:%02d:%02d.%03d",
				dt->year, dt->month, dt->day,
				dt->hour, dt->minute, dt->second, dt->millisecond);
		}
		else
		{
			k_snprintf(buf, size, "%04d-%02d-%02d %02d:%02d:%02d",
				dt->year, dt->month, dt->day,
				dt->hour, dt->minute, dt->second);
		}
	}
}


//////////////////////////////////////////////////////////////////////////
// 날짜

// 월별 일수
static const kbyte _k_date_day_per_month[2][13] =
{
	{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, },
	{0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, },
};
static const kushort _k_date_day_per_year[2][14] =
{
	{0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365, },
	{0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366, },
};

// 윤년?
static kcham _k_date_test_leap(int y)
{
	return y > 0 && ((((y % 4) == 0) && ((y % 100) != 0)) || (y % 400) == 0);
}

// 맞는 날짜?
static kcham _k_date_test_valid(int y, int m, int d)
{
	return
		m > 0 && m < 13 &&
		d > 0 && y > 0 &&
		d <= _k_date_test_leap(y)
		? _k_date_day_per_month[1][m]
		: _k_date_day_per_month[0][m];
}

/// 날짜 년월일 설정.
/// @date 2013-12-22
/// @param self 자기 자신 참조 포인터.
/// @param y    년.
/// @param m    월.
/// @param d    일.
/// @return 성공하면 참을 실패하면 거짓을 반환한다.
kcham k_date_set_ymd(kDate* self, int y, int m, int d)
{
	if (!_k_date_test_valid(y, m, d))
		return FALSE;
	else
	{
		self->_fj = FALSE;
		self->_fy = TRUE;
		self->year = y;
		self->month = m;
		self->day = d;
		self->_dummy = 0;
		return TRUE;
	}
}

/// 날짜 율리우스 설정.
/// @date 2013-12-22
/// @param self   자기 자신 참조 포인터.
/// @param julius 율리우스값.
/// @return 성공하면 참을 실패하면 거짓을 반환한다.
kcham k_date_set_julius(kDate* self, kuint julius)
{
	if (julius == 0)
		return FALSE;
	else
	{
		self->_fj = TRUE;
		self->_fy = FALSE;
		self->j = julius;
		self->_dummy = 0;
		return TRUE;
	}
}

/// 날짜 TIME_T 설정.
/// @date 2013-12-22
/// @param self 자기 자신 참조 포인터.
/// @param tt   time_t.
/// @return 성공하면 참을 실패하면 거짓을 반환한다.
kcham k_date_set_timet(kDate* self, const time_t* tt)
{
	struct tm tm;
	k_localtime(&tm, tt);
	return k_date_set_ymd(self, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
}

/// 날짜 시간 값 설정.
/// @date 2013-12-22
/// @param self 자기 자신 참조 포인터.
/// @param tv   시간값.
/// @return 성공하면 참을 실패하면 거짓을 반환한다.
kcham k_date_set_timeval(kDate* self, const kTimeVal* tv)
{
	time_t tt = (time_t)tv->tv_sec;
	return k_date_set_timet(self, &tt);
}

/// 날짜 오늘로 설정.
/// @date 2013-12-22
/// @param self 자기 자신 참조 포인터.
/// @return 성공하면 참을 실패하면 거짓을 반환한다.
kcham k_date_now(kDate* self)
{
	kDateTime dt;
	k_now(&dt);
	return k_date_set_ymd(self, dt.year, dt.month, dt.day);
}

// 율리우스 업데이트
static kcham _k_date_update_julius(kDate* self)
{
	int y;
	int i;

	k_return_value_if_fail(self->_fy != 0, FALSE);
	k_return_value_if_fail(self->_fj == 0, FALSE);
	k_return_value_if_fail(_k_date_test_valid(self->year, self->month, self->day), FALSE);

	y = self->year - 1;
	self->j = y * 365;
	self->j += (y >>= 2);
	self->j -= (y /= 25);
	self->j += y >> 2;

	i = _k_date_test_leap(self->year) ? 1 : 0;
	self->j += _k_date_day_per_year[i][self->month] + self->day;

	if (self->j == 0)
		return FALSE;
	else
	{
		self->_fj = TRUE;
		return TRUE;
	}
}

// 년월일 업데이트
static kcham _k_date_update_ymd(kDate* self)
{
	kuint z[6];

	k_return_value_if_fail(self->_fy == 0, FALSE);
	k_return_value_if_fail(self->_fj != 0, FALSE);
	k_return_value_if_fail(self->j > 0, FALSE);

	z[0] = self->j + 1721425 + 32045;
	z[1] = (4 * (z[0] + 36524)) / 146097 - 1;
	z[2] = z[0] - (146097 * z[1]) / 4;
	z[3] = (4 * (z[2] + 365)) / 1461 - 1;
	z[4] = z[2] - ((1461 * z[3]) / 4);
	z[5] = (5 * (z[4] - 1) + 2) / 153;

	self->year = 100 * z[1] + z[3] - 4800 + (z[5] / 10);
	self->month = z[5] + 3 - (12 * (z[5] / 10));
	self->day = z[4] - (153 * z[5] + 2) / 5;

	return self->_fy = TRUE;
}

/// 날짜의 주.
/// @date 2013-12-22
/// @param self 자기 자신 참조 포인터.
/// @return int.
int k_date_to_week(kDate* self)
{
	if (!self->_fj)
		_k_date_update_julius(self);

	return !self->_fj ? 0 : ((self->_fj - 1) % 7) + 1;
}

/// 날짜의 년.
/// @date 2013-12-22
/// @param self 자기 자신 참조 포인터.
/// @return int.
int k_date_to_year(kDate* self)
{
	if (!self->_fy)
		_k_date_update_ymd(self);

	return !self->_fy ? 0 : self->year;
}

/// 날짜의 월.
/// @date 2013-12-22
/// @param self 자기 자신 참조 포인터.
/// @return int.
int k_date_to_month(kDate* self)
{
	if (!self->_fy)
		_k_date_update_ymd(self);

	return !self->_fy ? 0 : self->month;
}

/// 날짜의 일.
/// @date 2013-12-22
/// @param self 자기 자신 참조 포인터.
/// @return int.
int k_date_to_day(kDate* self)
{
	if (!self->_fy)
		_k_date_update_ymd(self);

	return !self->_fy ? 0 : self->day;
}

/// 날짜의 율리우스.
/// @date 2013-12-22
/// @param self 자기 자신 참조 포인터.
/// @return int.
int k_date_to_julius(kDate* self)
{
	if (!self->_fj)
		_k_date_update_julius(self);

	return !self->_fj ? 0 : self->j;
}

/// 날짜의 년중일.
/// @date 2013-12-22
/// @param self 자기 자신 참조 포인터.
/// @return int.
int k_date_to_doy(kDate* self)
{
	if (!self->_fy)
		_k_date_update_ymd(self);

	return _k_date_day_per_year[_k_date_test_leap(self->year) ? 1 : 0][self->month] + self->day;
}

/// 날짜의 년중주.
/// @date 2013-12-22
/// @param self 자기 자신 참조 포인터.
/// @return int.
int k_date_to_woy(kDate* self)
{
	kuint j, l;
	kuint d, d4;

	if (!self->_fj)
		_k_date_update_julius(self);

	j = self->j + 1721425;
	d4 = (j + 31741 - (j % 7)) % 146097 % 36524 % 1461;
	l = d4 / 1460;
	d = ((d4 - l) % 365) + l;

	return d / 7 + 1;
}

/// 날짜의 12지.
/// @date 2013-12-22
/// @param self 자기 자신 참조 포인터.
/// @return int.
int k_date_to_zi(kDate* self)
{
	int n;

	if (!self->_fj)
		_k_date_update_julius(self);

	if (!self->_fj)
		return 0;

	n = (self->j - 1) % 12;
	return (n < 0) ? n + 12 : n;
}

/// 날짜의 10간.
/// @date 2013-12-22
/// @param self 자기 자신 참조 포인터.
/// @return int.
int k_date_to_gan(kDate* self)
{
	// 이거 테스트해봐야함
	int n;

	if (!self->_fj)
		_k_date_update_julius(self);

	if (!self->_fj)
		return 0;

	n = (self->j - 1) % 10;
	return (n < 0) ? n + 10 : n;
}

/// 날짜 사이 일수 계산.
/// @date 2013-12-22
/// @param [in] left  left.
/// @param [in] right right.
/// @return int.
int k_date_between(kDate* left, kDate* right)
{
	return k_date_to_julius(left) - k_date_to_julius(right);
}

/// 날짜 일 수 더하기.
/// @date 2013-12-22
/// @param self 자기 자신 참조 포인터.
/// @param days 일 수.
void k_date_add(kDate* self, kint days)
{
	if (!self->_fj)
		_k_date_update_julius(self);

	self->j += days;
	self->_fy = FALSE;
}

/// 날짜 해시.
/// @date 2013-12-22
/// @param self 자기 자신 참조 포인터.
/// @return ksize_t.
ksize_t k_date_hash(kDate* self)
{
	if (!self->_fj)
		_k_date_update_julius(self);

	return self->j;
}

/// 날짜 크기 비교.
/// @date 2013-12-22
/// @param [in] left  left.
/// @param [in] right right.
/// @return int.
int k_date_comp(kDate* left, kDate* right)
{
	for (;;)
	{
		if (left->_fj && right->_fj)
		{
			if (left->j < right->j)
				return -1;
			else if (left->j > right->j)
				return 1;
			else
				return 0;
		}
		else if (left->_fy && right->_fy)
		{
			if (left->year < right->year)
				return -1;
			else if (left->year > right->year)
				return 1;
			else
			{
				if (left->month < right->month)
					return -1;
				else if (left->month > right->month)
					return 1;
				else
				{
					if (left->day < right->day)
						return -1;
					else if (left->day > right->day)
						return 1;
					else
						return 0;
				}
			}
		}
		else
		{
			if (!left->_fj)
				_k_date_update_julius(left);

			if (!right->_fj)
				_k_date_update_julius(right);

			if (!left->_fj)
				return 0;

			if (!right->_fj)
				return 0;
		}
	}
}


//////////////////////////////////////////////////////////////////////////
// GUID

//
void k_guid(kGuid* guid)
{
	kDateVal dv;

	k_return_if_fail(guid);

	k_dateval_now(&dv);

	guid->A = dv.day | (dv.hour << 16);
	guid->B = dv.month | (dv.second << 16);
	guid->C = dv.millisecond | (dv.minute << 16);
	guid->D = dv.year ^ k_cycles();
}

//
kint k_guid_eq(const kGuid* l, const kGuid* r)
{
	kuint
		a = l->A ^ r->A,
		b = l->B ^ r->B,
		c = l->C ^ r->C,
		d = l->D ^ r->D;
	return (a | b | c | d) == 0;
}

//
kint k_guid_comp(const kGuid* l, const kGuid* r)
{
	return
		(l->A < r->A) ? 1 : (l->A > r->A) ? -1 :
		(l->B < r->B) ? 1 : (l->B > r->B) ? -1 :
		(l->C < r->C) ? 1 : (l->C > r->C) ? -1 :
		(l->D < r->D) ? 1 : (l->D > r->D) ? -1 : 0;
}

//
char* k_guid_tos(const kGuid* guid, kint mtd, char* buf, kint size)
{
	kGuid tmpguid;

	if (!buf || size <= 0)
		return NULL;

	if (!guid)
	{
		k_guid(&tmpguid);
		guid = &tmpguid;
	}

	switch (mtd)
	{
		case KGUIDTS_UNIQUE:
			k_snprintf(buf, size, "%08X-%08X-%08X-%08X", guid->A, guid->B, guid->C, guid->D);
			break;

		case KGUIDTS_DIGIT:
			k_snprintf(buf, size, "%08X-%04X-%04X-%04X-%04X%08X", guid->A, guid->B >> 16, guid->B & 0xFFFF, guid->C >> 16, guid->C & 0xFFFF, guid->D);
			break;

		case KGUIDTS_DIGIT_BRACE:
			k_snprintf(buf, size, "{%08X-%04X-%04X-%04X-%04X%08X}", guid->A, guid->B >> 16, guid->B & 0xFFFF, guid->C >> 16, guid->C & 0xFFFF, guid->D);
			break;

		case KGUIDTS_GIDIT_PARENTHESE:
			k_snprintf(buf, size, "(%08X-%04X-%04X-%04X-%04X%08X)", guid->A, guid->B >> 16, guid->B & 0xFFFF, guid->C >> 16, guid->C & 0xFFFF, guid->D);
			break;

		default:
			k_snprintf(buf, size, "%08X%08X%08X%08X", guid->A, guid->B, guid->C, guid->D);
			break;
	}

	return buf;
}
